import 'dart:convert';
import 'dart:io';

import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:dio/dio.dart';
import 'package:dio/io.dart';
import 'package:flutter/material.dart';
import '../util.dart' hide Response, FormData;

import 'net_code.dart';
import 'net_exception.dart';
import 'net_request_interceptor.dart';

import 'net_error_interceptor.dart';
import 'urlpath.dart';

typedef HttpErrorCallback = Function(NetErrorResponse res);

class HttpMan {
  static const int connetTimeout = 10000;
  static const int receiveTimeout = 600000;
  late Dio _dio;
  CancelToken cancelToken = CancelToken();

  factory HttpMan() => _getInstance();
  static HttpMan get instance => _getInstance();

  static HttpMan? _instance;

  static HttpMan _getInstance() {
    _instance ??= HttpMan._internal();
    return _instance!;
  }

  HttpMan._internal() {
    BaseOptions options = BaseOptions(
        connectTimeout: const Duration(milliseconds: connetTimeout),
        receiveTimeout: const Duration(milliseconds: receiveTimeout));
    _dio = Dio(options);
    _dio.interceptors.add(NetRequestInterceptor());
    _dio.interceptors.add(NetErrorInterceptor());
    var adapter = (_dio.httpClientAdapter as IOHttpClientAdapter);
    adapter.createHttpClient = () {
      var client = HttpClient();
      client.badCertificateCallback =
          (X509Certificate cert, String host, int port) {
        return true;
      };
      return client;
    };
  }

  Future<NetBaseReponse<T>> post<T>(
    String path, {
    Map<String, dynamic>? args,
    bool needForm = false,
    bool needBaseResp = true,
    bool loadding = true,
    Map<String, dynamic>? headers,
    HttpErrorCallback? onErr,
    T Function(Map<String, dynamic> json)? fromJsonT,
  }) async {
    Get.log('url---$path');
    return checkNet<T>(
      path,
      args: args,
      isPost: true,
      needForm: needForm,
      needBaseResp: needBaseResp,
      loadding: loadding,
      headers: headers,
      onErr: onErr,
      fromJsonT: fromJsonT,
    );
  }

  Future<NetBaseReponse<T>> postWithoutName<T>(
    String path, {
    dynamic args,
    bool needBaseResp = true,
    bool loadding = true,
    Map<String, dynamic>? headers,
    HttpErrorCallback? onErr,
    T Function(Map<String, dynamic> json)? fromJsonT,
  }) async {
    Get.log('url---$path');
    return checkNet<T>(
      path,
      args: args,
      isPost: true,
      needForm: false,
      needBaseResp: needBaseResp,
      loadding: loadding,
      headers: headers,
      onErr: onErr,
      fromJsonT: fromJsonT,
    );
  }

  Future<NetBaseReponse<T>> get<T>(
    String path, {
    Map<String, dynamic>? args,
    bool needForm = false,
    bool needBaseResp = true,
    bool loadding = true,
    HttpErrorCallback? onErr,
    T Function(Map<String, dynamic> json)? fromJsonT,
  }) async {
    return checkNet<T>(
      path,
      args: args,
      isPost: false,
      needForm: needForm,
      needBaseResp: needBaseResp,
      loadding: loadding,
      onErr: onErr,
      fromJsonT: fromJsonT,
    );
  }

  Future<NetBaseReponse<T>> checkNet<T>(
    String path, {
    dynamic args,
    bool isPost = true,
    bool needForm = false,
    bool needBaseResp = true,
    bool loadding = true,
    Map<String, dynamic>? headers,
    HttpErrorCallback? onErr,
    T Function(Map<String, dynamic> json)? fromJsonT,
  }) async {
    var connectivityResult = await Connectivity().checkConnectivity();
    if (connectivityResult == ConnectivityResult.none) {
      await Future.delayed(const Duration(seconds: 2));
      return checkNet<T>(
        path,
        args: args,
        isPost: isPost,
        needForm: needForm,
        needBaseResp: needBaseResp,
        loadding: loadding,
        headers: headers,
        onErr: onErr,
        fromJsonT: fromJsonT,
      );
    }
    return request<T>(
      path,
      args: args,
      isPost: isPost,
      needForm: needForm,
      needBaseResp: needBaseResp,
      loadding: loadding,
      headers: headers,
      onErr: onErr,
      fromJsonT: fromJsonT,
    );
  }

  Future<NetBaseReponse<T>> request<T>(
    String path, {
    dynamic args,
    bool isPost = true,
    bool needForm = false,
    bool needBaseResp = true,
    bool loadding = true,
    Map<String, dynamic>? headers,
    HttpErrorCallback? onErr,
    T Function(Map<String, dynamic> json)? fromJsonT,
  }) async {
    if (loadding) {
      Hud.show();
    }
    if (!path.startsWith("http")) {
      path = UrlPath.baseUrl + path;
    }

    Response _response = isPost
        ? await _dio
            .post(
            path,
            data: needForm ? (FormData.fromMap(args ?? {})) : args,
            options: Options(
              headers: headers,
            ),
            cancelToken: cancelToken,
          )
            .catchError((e) {
            return resoveError(e, onErr);
          })
        : await _dio
            .get(
            path,
            queryParameters: args,
            cancelToken: cancelToken,
          )
            .catchError((e) {
            return resoveError(e, onErr);
          });
    if (Hud.isShow && loadding) {
      Hud.dismiss();
    }
    Map<String, dynamic> data;
    if (_response.data is String) {
      data = jsonDecode(_response.data);
    } else {
      data = _response.data;
    }
    Get.log('$path\n${jsonEncode(args ?? {})}\n${jsonEncode(data)}');
    if (!needBaseResp) {
      var res = NetBaseReponse<T>.origin(json: data);
      if (!res.isSuccess) {
        Hud.toast(res.msg);
      }
      return res;
    }
    if (_response.statusCode == 200) {
      var res = NetBaseReponse<T>.response(
        res: NetReponse.fromJson(data),
        fromJsonT: fromJsonT,
      );
      if (res.code == NetResponseCode.invalidToken &&
          Get.currentRoute != RouterPath.login) {
        Get.offAllNamed(RouterPath.login);
      }

      if (!res.isSuccess) {
        Hud.toast(res.msg);
      }
      return res;
    } else {
      debugPrint(_response.toString());
      Hud.toast('网络开小差了，请稍后重试');
      return NetBaseReponse.code(code: NetResponseCode.networkError);
    }
  }

  Future<NetBaseReponse> requestUri<T>(
    Uri uri,
  ) async {
    var connectivityResult = await Connectivity().checkConnectivity();
    if (connectivityResult == ConnectivityResult.none) {
      return NetBaseReponse.code(code: NetResponseCode.networkError);
    }
    Response _response = await _dio.requestUri(
      uri,
      cancelToken: cancelToken,
    );
    return NetBaseReponse<T>.origin(json: _response.data);
  }

  Future<NetBaseReponse> stream(String path,
      {required String filePath,
      bool needBaseResp = true,
      bool loadding = true,
      Map<String, dynamic>? headers,
      M}) async {
    if (Hud.isShow && loadding) {
      Hud.dismiss();
    }
    if (loadding) {
      Hud.show();
    }
    if (!path.startsWith("http")) {
      path = UrlPath.baseUrl + path;
    }
    var file = File(filePath);
    var data = file.readAsBytesSync();
    // var data = await File(filePath).openRead().single;
    headers ??= {};
    headers[Headers.contentLengthHeader] = data.length;
    Response _response = await _dio.post(
      path,
      data: data,
      options: Options(
        sendTimeout: const Duration(seconds: 30),
        receiveTimeout: const Duration(seconds: 30),
        headers: headers,
      ),
      cancelToken: cancelToken,
    );
    if (Hud.isShow && loadding) {
      Hud.dismiss();
    }
    Get.log('$path\n${jsonEncode(_response.data)}');
    if (!needBaseResp) {
      var res = NetBaseReponse.origin(json: _response.data);

      return res;
    }
    if (_response.statusCode == 200) {
      var res =
          NetBaseReponse.response(res: NetReponse.fromJson(_response.data));
      if (res.code == NetResponseCode.invalidToken &&
          Get.currentRoute != RouterPath.login) {
        Get.offAllNamed(RouterPath.login);
      }

      return res;
    } else {
      debugPrint(_response.toString());
      Hud.toast('网络开小差了，请稍后重试');
      return NetBaseReponse.code(code: NetResponseCode.networkError);
    }
  }

  Future<NetBaseReponse> upload(
    String path, {
    required FormData data,
    bool needBaseResp = true,
    ProgressCallback? onSendProgress,
    ProgressCallback? onReceiveProgress,
  }) async {
    var connectivityResult = await Connectivity().checkConnectivity();
    if (connectivityResult == ConnectivityResult.none) {
      return NetBaseReponse.code(code: NetResponseCode.networkError);
    }
    if (!path.startsWith("http")) {
      path = UrlPath.baseUrl + path;
    }
    Hud.show();
    Response _response = await _dio.post(
      path,
      data: data,
      options: Options(receiveTimeout: const Duration(seconds: 30)),
      cancelToken: cancelToken,
      onSendProgress: onSendProgress,
      onReceiveProgress: onReceiveProgress,
    );
    if (Hud.isShow) {
      Hud.dismiss();
    }
    Get.log('$path\n${data.files}\n${jsonEncode(_response.data)}');
    if (!needBaseResp) {
      var res = NetBaseReponse.origin(json: _response.data);

      return res;
    }
    if (_response.statusCode == 200) {
      var res =
          NetBaseReponse.response(res: NetReponse.fromJson(_response.data));
      if (res.code == NetResponseCode.invalidToken &&
          Get.currentRoute != RouterPath.login) {
        Get.offAllNamed(RouterPath.login);
      }

      return res;
    } else {
      debugPrint(_response.toString());
      Hud.toast('网络开小差了，请稍后重试');
      return NetBaseReponse.code(code: NetResponseCode.networkError);
    }
  }

  Future<NetBaseReponse> download(
    String path,
    dynamic savePath, {
    void Function(int, int)? onReceiveProgress,
    Map<String, dynamic>? queryParameters,
  }) async {
    var connectivityResult = await Connectivity().checkConnectivity();
    if (connectivityResult == ConnectivityResult.none) {
      return NetBaseReponse.code(code: NetResponseCode.networkError);
    }

    Response response = await _dio.download(
      path,
      savePath,
      cancelToken: cancelToken,
      onReceiveProgress: onReceiveProgress,
      queryParameters: queryParameters,
    );
    if (response.statusCode == 200) {
      return NetBaseReponse.code(code: NetResponseCode.success);
    } else {
      return NetBaseReponse.code(code: NetResponseCode.networkError);
    }
  }

/*
   * 取消请求
   *
   * 同一个cancel token 可以用于多个请求，当一个cancel token取消时，所有使用该cancel token的请求都会被取消。
   * 所以参数可选
   */
  void cancelRequest({CancelToken? token}) {
    token ?? cancelToken.cancel('canceled');
  }

  Response resoveError(dynamic error, HttpErrorCallback? callback) {
    var dr = Response(requestOptions: RequestOptions());

    if (error is DioException) {
      DioException exception = error;
      Get.log('exception.response.data----${exception.response?.data}');
      Get.log(exception.toString());

      if (callback != null) {
        Get.log('callback');
        callback(exception.response?.data);
      }
      return exception.response ?? dr;
    }
    return dr;
  }
}
